/*
 * ===========================================================================
 *
 *      Filename: io_tools.c
 *   Description: some useful io operation
 *        Author: Ye wenhao, wenhaoye@126.com
 *  Organization: 
 *
 * ===========================================================================
 */
#include "io_tools.h"

// read until timeout, error, specific size.
int fd_read(int fd, void *data, size_t size)
{
    if(fd < 0 || data == NULL){
        DBG("invalid args");
        return -1;
    }

    if(size == 0)
        return 0;

    ssize_t ret = -1;
_R_AGAIN:
    ret = read(fd, data, size);
    if(ret < 0){
        if(errno == EINTR)
            goto _R_AGAIN;

        if(errno == EAGAIN || errno == EWOULDBLOCK){
            DBG("EAGAIN");
            return 0;
        }

        return -1;
    }

    return ret;
}

int fd_write(int fd, void *data, size_t len)
{
    if(fd < 0 || data == NULL){
        DBG("invalid args");
        return -1;
    }

    if(len == 0)
        return 0;

    ssize_t ret = -1;
_W_AGAIN:
    ret = write(fd, data, len);
    if(ret < 0){
        if(errno == EINTR)
            goto _W_AGAIN;

        if(errno == EAGAIN || errno == EWOULDBLOCK){
            DBG("EAGAIN");
            return 0;
        }

        return -1;
    }

    return ret;
}

int fd_readv(int fd, struct iovec *iov, size_t count)
{
    if(fd < 0 || iov == NULL){
        DBG("invalid args");
        return -1;
    }

    size_t i;
    int ret;
    int rb = 0;
    for(i = 0; i < count; ++i){
        ret = fd_read(fd, iov[i].iov_base, iov[i].iov_len);
        if(ret < 0)
            return ret;
        
        rb += ret;
        if(ret == 0)
            break;
        if(ret < iov[i].iov_len)
            break;
    }

    return rb;
}

int fd_writev(int fd, struct iovec *iov, size_t count)
{
    if(fd < 0 || iov == NULL){
        DBG("invalid args");
        return -1;
    }

    size_t i;
    int ret;
    int wb = 0;
    for(i = 0; i < count; ++i){
        ret = fd_write(fd, iov[i].iov_base, iov[i].iov_len);
        if(ret < 0)
            return ret;

        wb += ret;
        if(ret == 0)
            break;
        if(ret < iov[i].iov_len)
            break;
    }

    return wb;
}

int tcp_server_create(const char *ip_str, uint16_t port, int backlog)
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd < 0){
        ERR("socket fail");
        return -1;
    }

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    if(ip_str[0] == '*')
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
    else
        addr.sin_addr.s_addr = inet_addr(ip_str);

    int reuseaddr = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));

    if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0){
        ERR("bind fail");
        goto _FAILE;
    }

    if(!backlog)
        backlog = 5;
    if(listen(fd, backlog) < 0){
        ERR("listen fail");
        goto _FAILE;
    }

    return fd;
    
_FAILE:
    close(fd);
    return -1;
}

int tcp_server_accept(int sock, struct sockaddr_in *cli_addr)
{
    struct sockaddr_in addr;
    socklen_t addr_len = sizeof(addr);
    int new_fd = accept(sock, (struct sockaddr *)&addr, &addr_len);
    if (new_fd < 0)
        return -1;
        
    if (cli_addr)
        memcpy(cli_addr, &addr, sizeof(addr));
        
    return new_fd;  
}

int connect_timeout(const char *ip, uint16_t port, int tm)
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd < 0)
        return -1;

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip);

	if (tm >= 0) 
		fd_set_nonblock(fd);
    int ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
	if (ret == 0) {
		DBG("connect success");
		goto _out;
	}

	if (tm >= 0)
		goto _err;

	if (errno != EINPROGRESS) {
		ERR("connect fail");
		goto _err;
	}
	DBG("connecting ...");

	fd_set rset, wset;
	FD_ZERO(&rset);
	FD_ZERO(&wset);
	FD_SET(fd, &rset);
	FD_SET(fd, &wset);

	struct timeval tv;
	tv.tv_sec = tm / 1000;
	tv.tv_usec = tm % 1000 * 1000;
	ret = select(fd+1, &rset, &wset, NULL, &tv);
	if (ret < 0) {
		ERR("select fail");
		goto _err;
	}
	
	if (ret == 0) {
		DBG("timeout");
		goto _err;
	}

	int error = -1;
	socklen_t len = sizeof(error);
	getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len);
	if (error) {
		DBG("connect fail");
		goto _err;
	}

	DBG("connect success");
_out:
	fd_set_block(fd);
	return fd;

_err:
	safe_close(fd);
	return -1;
}

int tcp_client_create(const char *ip_str, uint16_t port)
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd < 0)
        return -1;

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip_str);
    if(connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
        goto _FAILE;

    return fd;

_FAILE:
    close(fd);
    return -1;
}

int udp_server_create(const char *ip_str, uint16_t port)
{
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd < 0){
        ERR("socket fail");
        return -1;
    }

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    if(ip_str[0] == '*')
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
    else
        addr.sin_addr.s_addr = inet_addr(ip_str);

    int reuseaddr = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));

    if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0){
        ERR("bind fail");
        goto _FAILE;
    }

    return fd;
    
_FAILE:
    close(fd);
    return -1;
}

int udp_client_create(const char *ip_str, uint16_t port)
{
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd < 0)
        return -1;

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip_str);

    if(connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0){
        ERR("connect fail");
        goto _FAILE;
    }

    return fd;
    
_FAILE:
    close(fd);
    return -1;
}

int unix_tcp_server_create(const char *path, int backlog)
{
    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if(fd < 0)
        return -1;

    unlink(path);
    
    struct sockaddr_un addr;
    socklen_t addr_len;
    memset(&addr, 0, sizeof(addr));
    strcpy(addr.sun_path, path);
    addr.sun_family = AF_UNIX;
    addr_len = sizeof(addr.sun_family) + strlen(addr.sun_path);
    if(bind(fd, (struct sockaddr *)&addr, addr_len) < 0)
        goto _FAIL;
    
    if(!backlog)
        backlog = 5;
    if(listen(fd, backlog) < 0)
        goto _FAIL;

    return fd;

_FAIL:
    close(fd);
    return -1;
}

int unix_tcp_client_create(const char *path)
{
    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if(fd < 0)
        return -1;

    struct sockaddr_un addr;
    socklen_t addr_len;
    memset(&addr, 0, sizeof(addr));
    strcpy(addr.sun_path, path);
    addr.sun_family = AF_UNIX;
    addr_len = sizeof(addr.sun_family) + strlen(addr.sun_path);
    if(connect(fd, (struct sockaddr *)&addr, addr_len) < 0){
        close(fd);
        return -1;
    }   

    return fd;
}

int unix_udp_server_create(const char *path)
{
    unlink(path);

    int sock = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (sock < 0)
        return -1;

    struct sockaddr_un addr;
    socklen_t addr_len;
    strcpy(addr.sun_path, path);
    addr.sun_family = AF_UNIX;
    addr_len = sizeof(addr.sun_family) + strlen(addr.sun_path);
    if(bind(sock, (struct sockaddr *)&addr, addr_len) < 0)
        goto _FAIL;

    return sock;

_FAIL:
    safe_close(sock);
    return -1;
}

int unix_udp_client_create(const char *path)
{
    int sock = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (sock < 0)
        return -1;

    struct sockaddr_un addr;
    socklen_t addr_len;
    strcpy(addr.sun_path, path);
    addr.sun_family = AF_UNIX;
    addr_len = sizeof(addr.sun_family) + strlen(addr.sun_path);
    if (connect(sock, (struct sockaddr *)&addr, addr_len) < 0)
        safe_close(sock);

    return sock;
}

int fd_set_nonblock(int fd)
{
    int val;
    if((val = fcntl(fd, F_GETFL, 0)) == -1){
        perror("fcntl get");
        return -1;
    }

	if (val & O_NONBLOCK)
		return 0;

    val |= O_NONBLOCK;
    if(fcntl(fd, F_SETFL, val) == -1){
        perror("fcntl set");
        return -1;
    }
    
    return 0;
}

int fd_set_block(int fd)
{
    int val;
    if((val = fcntl(fd, F_GETFL, 0)) == -1){
        perror("fcntl get");
        return -1;
    }

	if (val & O_NONBLOCK) {
		val &= (~O_NONBLOCK);
		if(fcntl(fd, F_SETFL, val) == -1){
			perror("fcntl set");
			return -1;
		}
	}
    
    return 0;
}
int get_file_size(const char *file_name, off_t *file_size)
{
    struct stat st;
    memset(&st, 0, sizeof(st));

    if(stat(file_name, &st) < 0){
        return -1;
    }

    *file_size = st.st_size;
    return 0;
}

